package com.dreamycode.paths;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;


/**
 * Implements the {@link Path}, {@link File} and {@link Folder} interfaces.
 * Uses a {@link java.io.File java.io.File} internally to do all the work.
 *
 * <p>Static factory methods create instances of each interface as well
 * as convert other types.
 *
 * @see Path
 * @see File
 * @see Folder
 *
 * @author Eli Doran <eli@elidoran.com>
 *
 * @since 1.0
 */
public final class Paths implements Path, Folder, File
{
    /* ************************************************************************
     *     STATIC
     * ***********************************************************************/

    /**
     * Returns a Folder list of the root folders of the file system.
     *
     * @since 1.0
     */
    public static List<Folder> getRoots()
    {
        return asFolders(Arrays.asList(java.io.File.listRoots()));
    }

    /**
     * Returns a Path representing the File.
     * Returns null if the ioFile is null.
     *
     * @param ioFile The file.
     *
     * @return The Path.
     *
     * @since 1.0
     */
    public static Path asPath(final java.io.File ioFile)
    {
        Path path = null;

        if (ioFile != null)
        {
            path = new Paths(ioFile);
        }

        return path;
    }

    /**
     * Returns a Path representing the specified path.
     * Returns null if the path is null or length 0.
     *
     * @param path The path.
     *
     * @return The Path.
     *
     * @since 1.0
     */
    public static Path asPath(final CharSequence path)
    {
        Path pathObject = null;

        if ((path != null) && (path.length() > 0))
        {
            final String string = path.toString();
            final java.io.File file = new java.io.File(string);
            pathObject = new Paths(file);
        }

        return pathObject;
    }


    public static Path asPath(final URL url)
    {
        Path result = null;

        if((url != null) && "file".equals(url.getProtocol()))
        {
            // remove the "file:/" part
            final String path = url.toExternalForm().substring(6);
            final java.io.File file = new java.io.File(path);
            result = new Paths(file);
        }

        return result;
    }

    /**
     * Returns a File representing the specified path.
     * Returns null if the path is null or length 0.
     *
     * @param path The path.
     *
     * @return The File.
     *
     * @since 1.0
     */
    public static File asFile(final CharSequence path)
    {
        File file = null;

        if ((path != null) && (path.length() > 0))
        {
            final String string = path.toString();
            final java.io.File javaFile = new java.io.File(string);

            if (isFile(javaFile))
            {
                file =  new Paths(javaFile);
            }
        }

        return file;
    }


    public static File asFile(final URL url)
    {
        File file = null;

        if((url != null) && "file".equals(url.getProtocol()))
        {
            // remove the "file:/" part
            final String path = url.toExternalForm().substring(6);
            final java.io.File javaFile = new java.io.File(path);

            if(isFile(javaFile))
            {
                file = new Paths(javaFile);
            }
        }

        return file;
    }

    /**
     * Returns a Folder representing the specified path.
     * Returns null if the path is null or length 0.
     *
     * @param path The path.
     *
     * @return The Folder.
     *
     * @since 1.0
     */
    public static Folder asFolder(final CharSequence path)
    {
        Folder folder = null;

        if ((path != null) && (path.length() > 0))
        {
            final String string = path.toString();
            final java.io.File file = new java.io.File(string);

            if (isFolder(file))
            {
                folder = new Paths(file);
            }
        }

        return folder;
    }


    public static Folder asFolder(final URL url)
    {
        Folder folder = null;

        if((url != null) && "file".equals(url.getProtocol()))
        {
            // remove the "file:/" part
            // what if it is "file://"
            final String string = url.toExternalForm();
            final int start = (string.charAt(6) == '/') ? 7 : 6;
            final String path = string.substring(start);
            final java.io.File file = new java.io.File(path);
            if(isFolder(file))
            {
                folder = new Paths(file);
            }
        }

        return folder;
    }

    /**
     * Returns a Folder representing the canonical version of the specified path.
     * Returns null if the path is null or length 0.
     *
     * @param path The path.
     *
     * @return The Folder.
     * @throws IOException The call to {@link java.io.File#getCanonicalFile()} may throw an IOException.
     *
     * @since 1.0
     */
    public static Folder asCanonicalFolder(final CharSequence path) throws IOException
    {
        Folder folder = null;

        if ((path != null) && (path.length() > 0))
        {
            final String string = path.toString();
            final java.io.File file = new java.io.File(string).getCanonicalFile();

            if (isFolder(file))
            {
                folder = new Paths(file);
            }
        }

        return folder;
    }


    public static Folder getCurrentFolder()
    {
        // this is not going to fail, the current working directory is fine...
        try {
            return Paths.asCanonicalFolder(".");
        } catch(final IOException io) {
            // TODO: make a specific exception
            io.printStackTrace();
            //throw new RuntimeException("Failed to get the current working directory using '.' and canonicalizing it.", io);
        }

        return null;
    }

    /**
     * Returns a Path[] representing the files passed in.
     * Returns an empty array if the array is null or length 0.
     *
     * @param ioFiles The List<File>.
     *
     * @return The Path[].
     *
     * @since 1.0
     */
    public static List<Path> asPaths(final Iterable<java.io.File> ioFiles)
    {
        final List<Path> paths = new LinkedList<Path>();

        if (ioFiles != null)
        {
            for (final java.io.File file : ioFiles)
            {
                paths.add(new Paths(file));
            }
        }

        return paths;
    }

    /**
     * Returns a File representing the file passed in.
     * Returns a null if the file is null or is a folder.
     *
     * @param ioFile The file.
     *
     * @return The File.
     *
     * @since 1.0
     */
    public static File asFile(final java.io.File ioFile)
    {
        File file = null;

        if (isFile(ioFile))
        {
            file = new Paths(ioFile);
        }

        return file;
    }

    /**
     * Returns a List<File> representing the files passed in.
     * Returns an empty array if the array is null or length 0.
     * Returned array will contain null values when the java.io.File in the corresponding array index
     * is NOT really a file (it's a directory).
     *
     * @param ioFiles The files.
     *
     * @return The List<File>.
     *
     * @since 1.0
     */
    public static List<File> asFiles(final Iterable<java.io.File> ioFiles)
    {
        final List<File> files = new LinkedList<File>();

        if (ioFiles != null)
        {
            for (final java.io.File file : ioFiles)
            {
                if (isFile(file))
                {
                    files.add(new Paths(file));
                }
            }
        }

        return files;
    }

    /**
     * Returns a Folder representing the file passed in.
     * Returns anull if the file is null or is not a folder.
     *
     * @param file The file.
     *
     * @return The Folder.
     *
     * @since 1.0
     */
    public static Folder asFolder(final java.io.File file)
    {
        Folder folder = null;

        if (isFolder(file))
        {
            folder = new Paths(file);
        }

        return folder;
    }

    /**
     * Returns a List<Folder> representing the files passed in.
     * Returns an empty array if the array is null or length 0.
     *
     * @param files The files.
     *
     * @return The List<Folder>.
     *
     * @since 1.0
     */
    public static List<Folder> asFolders(final Iterable<java.io.File> files)
    {
        final List<Folder> folders = new LinkedList<Folder>();

        if (files != null)
        {
            for (final java.io.File file : files)
            {
                if (isFolder(file))
                {
                    folders.add(new Paths(file));
                }
            }
        }

        return folders;
    }



    private static boolean isFile(final java.io.File file)
    {
        return (file != null) && ((! file.exists()) || file.isFile());
    }

    private static boolean isFolder(final java.io.File file)
    {
        return (file != null) && ((! file.exists()) || file.isDirectory());
    }

    // TODO: create one accepting the folder and add a non-static version to
    // the Folder interface.
    public static File createTempFile(final CharSequence prefix, final CharSequence suffix)
    {
        try {
            // according to java.io.File this must be non-null AND at least 3 characters.
            final String p = prefix.toString();
            final String s = (null == prefix) ? null : suffix.toString();
            final java.io.File javaFile = java.io.File.createTempFile(p, s);
            final File file = new Paths(javaFile);
            return file;
        } catch(final IOException e) {
            // TODO: create a specific exception class
            throw new RuntimeException(String.format("Failed to crate a temp file (%s, %s)", prefix, suffix), e);
        }
    }

    /* ************************************************************************
     *     INSTANCE
     * ***********************************************************************/

    /**
     * The file this represents.
     *
     * @since 1.0
     */
    private final java.io.File _file;

    /**
     * The file lock on this file if it is currently locked.
     *
     * @since 1.0
     */
    private FileLock _lock;

    Paths(final java.io.File file)
    {
        _file = file;
    }

    /* ************************************************************************
     *     Path implementation
     * ***********************************************************************/

    @Override public java.io.File asJavaFile()
    {
        return _file;
    }

    @Override public CharSequence getName()
    {
        return _file.getName();
    }

    @Override public CharSequence getPath()
    {
        return _file.getPath();
    }

    @Override public CharSequence getFullPath()
    {
        return _file.getAbsolutePath();
    }

    @Override public Folder getFolder()
    {
        return new Paths(_file.getParentFile());
    }

    @Override public boolean isReal()
    {
        return _file.exists();
    }

    @Override public boolean isRealFile()
    {
        return isReal() && isFile();
    }

    @Override public boolean isRealFolder()
    {
        return isReal() && isFolder();
    }

    @Override public boolean isFile()
    {
        return _file.isFile();
    }

    @Override public boolean isFolder()
    {
        return _file.isDirectory();
    }

    @Override public boolean isHidden()
    {
        return _file.isHidden();
    }

    @Override public boolean isFull()
    {
        return _file.isAbsolute();
    }

    @Override public File asFile()
    {
        // if this is a File or it's not yet created (Real) then return this.
        // we could create a new Paths instance with the same inner java.io.File
        // instance but, why?
        if (isFile() || ! isReal())
        {
            return this;
        }

        else
        {
            return null;
        }
    }

    @Override public Folder asFolder()
    {
        // if this is a Folder or it's not yet created (Real) then return this.
        // we could create a new Paths instance with the same inner java.io.File
        // instance but, why?
        if (isFolder() || ! isReal())
        {
            return this;
        }

        else
        {
            return null;
        }
    }

    @Override public URL asUrl()
    {
        try {
            // TODO: reinclude my 'Url'
            return _file.toURI().toURL();
        } catch (final MalformedURLException e) {
            throw new RuntimeException("Failed to convert to URL: "+getFullPath(), e);
        }
    }

    @Override public URI asUri()
    {
        return _file.toURI();
    }

    @Override public Properties asProperties() throws IOException
    {
        return asProperties(new Properties());
    }

    @Override public Properties asProperties(final Properties props) throws IOException
    {
        final InputStream inputStream = getInputStream();

        props.load(inputStream);

        return props;
    }

    @Override public boolean lock()
    {
        return lock(true);
    }

    @Override public boolean lock(final boolean append)
    {
        if (isLocked())
        {
            return true;
        }

        try
        {
            // if it's real we can be sure its containing folders are too
            // if not and its folder is not real then create them.
            if (! isReal() && ! getFolder().isReal())
            {
                // getFile() gets the java.io.File
                asJavaFile().getParentFile().mkdirs();
            }

            // the file lock holds a reference to the FileChannel
            // so it shouldn't be auto-closed which would release
            // the lock.
            _lock = getOutChannel(append).tryLock();
        }
        catch(final java.nio.channels.ClosedChannelException cce)
        {
            // since we just created the FileChannel it shouldn't be
            // closed...
        }
        catch(final java.nio.channels.OverlappingFileLockException ofle)
        {
            // we're locking the whole thing. anyone doing partial locks
            // should do it directly. the lock(), unlock(), isLocked() methods
            // are for conveniently doing the simple case.
        }
        catch(final IOException io)
        {
            io.printStackTrace();
        }

        return isLocked();
    }

    @Override public boolean unlock()
    {
        if (_lock != null)
        {
            try
            {
                final FileChannel channel = _lock.channel();

                // this will also release the file lock
                channel.close();
            }
            catch(final java.nio.channels.ClosedChannelException cce)
            {
                // if it is closed then the lock has been released...right?
                // so ignore
            }
            catch(final IOException io)
            {
                io.printStackTrace();
            }

            _lock = null;
        }

        return true;
    }

    @Override public boolean isLocked()
    {
        return (_lock != null) && _lock.isValid();
    }

    // the file lock holds a reference to its channel so it won't be
    // garbage collected or auto-closed right?
    @Override public FileLock getLock()
    {
        return _lock;
    }

    @Override public long getModified()
    {
        return _file.lastModified();
    }

    @Override public boolean setModified(final long time)
    {
        return _file.setLastModified(time);
    }

    @Override public boolean rename(final CharSequence name)
    {
        final java.io.File newFile =
            new java.io.File(_file.getParentFile(), name.toString());

        return _file.renameTo(newFile);
    }

    @Override public boolean moveTo(final CharSequence path)
    {
        final java.io.File newFile = new java.io.File(path.toString());

        return _file.renameTo(newFile);
    }

    // TODO: change to 'moveTo'
    @Override public boolean renameTo(final File file)
    {
        return _file.renameTo(file.asJavaFile());
    }

    // TODO: change to 'moveTo'
    @Override public boolean renameTo(final java.io.File path)
    {
        return _file.renameTo(path);
    }

    // TODO: add copyTo

    @Override public boolean delete()
    {
        return _file.delete();
    }

    @Override public boolean deleteRecursively()
    {
        final boolean result = (_file.isDirectory()) ? deleteFolder(_file) : false;

        return result;
    }

    // TODO: add some logging output here ...
    private boolean deleteFolder(final java.io.File folder)
    {
        boolean result = true;

        // this could create a huge deep stack ... could we go thru it some other way?
        for(final java.io.File file : folder.listFiles())
        {
            if(file.isDirectory())
            {
                // if it fails, store the failure
                if( ! deleteFolder(file))
                {
                    result = false;
                }
            }
            else
            {
                // if it fails, store the failure
                if( ! file.delete())
                {
                    result = false;
                }
            }
        }

        // now that it is empty (should be), delete it
        // if it fails, store the failure
        if( ! folder.delete())
        {
            result = false;
        }

        return result;
    }


    /* ************************************************************************
     *     File implementation
     * ***********************************************************************/

    @Override public boolean isReadable()
    {
        return _file.canRead();
    }

    @Override public boolean isWritable()
    {
        return _file.canWrite();
    }

    @Override public FileInputStream getInputStream() throws IOException
    {
        return new FileInputStream(_file);
    }

    @Override public BufferedInputStream getBufferedInputStream() throws IOException
    {
        return new BufferedInputStream(getInputStream());
    }

    @Override public BufferedInputStream getBufferedInputStream(final int size) throws IOException
    {
        return new BufferedInputStream(getInputStream(), size);
    }

    @Override public FileReader getReader() throws IOException
    {
        return new FileReader(_file);
    }

    @Override public BufferedReader getBufferedReader() throws IOException
    {
        return new BufferedReader(getReader());
    }

    @Override public BufferedReader getBufferedReader(final int size) throws IOException
    {
        return new BufferedReader(getReader(), size);
    }

    @Override public FileOutputStream getOutputStream() throws IOException
    {
        return new FileOutputStream(_file);
    }

    @Override public FileOutputStream getOutputStream(final boolean append) throws IOException
    {
        if (append && _file.exists())
            return new FileOutputStream(_file, append);
        else
            return new FileOutputStream(_file);
    }

    @Override public BufferedOutputStream getBufferedOutputStream() throws IOException
    {
        return new BufferedOutputStream(getOutputStream());
    }

    @Override public BufferedOutputStream getBufferedOutputStream(final boolean append) throws IOException
    {
        return new BufferedOutputStream(getOutputStream(append));
    }

    @Override public BufferedOutputStream getBufferedOutputStream(final int size) throws IOException
    {
        return new BufferedOutputStream(getOutputStream(false), size);
    }

    @Override
    public BufferedOutputStream getBufferedOutputStream(final int size, final boolean append) throws IOException
    {
        return new BufferedOutputStream(getOutputStream(append), size);
    }

    @Override public FileWriter getWriter() throws IOException
    {
        return new FileWriter(_file);
    }

    @Override public FileWriter getWriter(final boolean append) throws IOException
    {
        return new FileWriter(_file, append);
    }

    @Override public BufferedWriter getBufferedWriter() throws IOException
    {
        return new BufferedWriter(getWriter());
    }

    @Override public BufferedWriter getBufferedWriter(final boolean append) throws IOException
    {
        return new BufferedWriter(getWriter(append));
    }

    @Override public BufferedWriter getBufferedWriter(final int size) throws IOException
    {
        return new BufferedWriter(getWriter(false), size);
    }

    @Override
    public BufferedWriter getBufferedWriter(final int size, final boolean append) throws IOException
    {
        return new BufferedWriter(getWriter(append), size);
    }

    @Override public PrintWriter getPrintWriter() throws IOException
    {
        return new PrintWriter(getBufferedWriter());
    }

    @Override public PrintWriter getPrintWriter(final boolean append) throws IOException
    {
        return new PrintWriter(getBufferedWriter(append));
    }

    @Override public PrintWriter getPrintWriter(final int size) throws IOException
    {
        return new PrintWriter(getBufferedWriter(size, false));
    }

    @Override public PrintWriter getPrintWriter(final int size, final boolean append) throws IOException
    {
        return new PrintWriter(getBufferedWriter(size, append));
    }

    @Override public FileChannel getInChannel() throws IOException
    {
        return getInputStream().getChannel();
    }

    @Override public FileChannel getOutChannel() throws IOException
    {
        return getOutputStream().getChannel();
    }

    @Override public FileChannel getOutChannel(final boolean append) throws IOException
    {
        return getOutputStream(append).getChannel();
    }

    @Override public long getLength()
    {
        return _file.length();
    }

    @Override public boolean setReadOnly()
    {
        return _file.setReadOnly();
    }

    @Override public String getExtension()
    {
        final String name = _file.getName();

        final int index = name.lastIndexOf('.');

        String extension = "";

        if (index > -1)
        {
            extension = name.substring(index+1);
        }

        return extension;
    }


    /* ************************************************************************
     *     Folder implementation
     * ***********************************************************************/

    @Override public boolean create()
    {
        return _file.mkdirs();
    }

    @Override public CharSequence getPathFrom(final Folder parent)
    {
        final CharSequence parentPath = parent.getFullPath();
        final CharSequence childPath = this.getFullPath();
        if(!childPath.toString().contains(parentPath))
            return null;
        final char lastCh = parentPath.charAt(parentPath.length() - 1);
        final int plus = (lastCh == '/' || lastCh == '\\') ? 0 : 1;
        final CharSequence subPath = childPath.subSequence(parentPath.length() + plus, childPath.length());
        return subPath;
    }

    @Override public Path getPath(final CharSequence name)
    {
        final java.io.File newFile =
            new java.io.File(_file.getParentFile(), name.toString());

        return new Paths(newFile);
    }

    @Override public List<Path> getPaths()
    {
        List<Path> result = Collections.emptyList();

        if (isRealFolder())
        {
            final java.io.File[] files = _file.listFiles();
            if (null != files && files.length > 0)
            {
                result = asPaths(Arrays.asList(files));
            }
        }

        return result;
    }

    @Override public List<Path> getPaths(final CharSequence pattern)
    {
        return asPaths(getJavaFiles(newFilter(pattern, null)));
    }

    @Override public List<Path> getPaths(final Pattern pattern)
    {
        return asPaths(getJavaFiles(newFilter(pattern, null)));
    }

    @Override public File getFile(final CharSequence name)
    {
        final java.io.File newFile =
            new java.io.File(_file, name.toString());

        if (newFile.exists() && !newFile.isFile())
        {
            return null;
        }

        return new Paths(newFile);
    }

    @Override public List<File> getFiles()
    {
        return asFiles(getJavaFiles(FILE_FILTER));
    }

    @Override public List<File> getFiles(final CharSequence pattern)
    {
        return asFiles(getJavaFiles(newFilter(pattern, Boolean.TRUE)));
    }

    @Override public List<File> getFiles(final Pattern pattern)
    {
        return asFiles(getJavaFiles(newFilter(pattern, Boolean.TRUE)));
    }

    @Override public List<File> getFiles(final FilenameFilter filter)
    {
        // not a FileFilter, need more than one use? or, just do it right here?
        List<File> result = Collections.emptyList();

        if (isRealFolder())
        {
            final java.io.File[] javaFiles = _file.listFiles(filter);

            if (javaFiles != null && javaFiles.length > 0)
            {
                final List<java.io.File> javaFileList = Arrays.asList(javaFiles);
                result = asFiles(javaFileList);
            }
        }

        return result;
    }

    @Override public List<File> getFiles(final FileFilter filter)
    {
        return asFiles(getJavaFiles(filter));
    }

    @Override public List<File> getFilesWithExtension(final String extension)
    {
        final FileFilter filter = new FileFilter() {
            @Override public boolean accept(final java.io.File file) {
                return file.isFile() && file.getName().endsWith(extension);
            }
        };
        return asFiles(getJavaFiles(filter));
    }

    @Override public List<File> getFilesWithExtensions(final String ... extensions)
    {
        final FileFilter filter = new FileFilter() {
            @Override public boolean accept(final java.io.File file) {
                Boolean accept = Boolean.FALSE;

                if (file.isFile())
                {
                    for(final String ext : extensions)
                    {
                        if (file.getName().endsWith(ext))
                        {
                            accept = Boolean.TRUE;
                            break;
                        }
                    }
                }

                return accept;
            }
        };
        return asFiles(getJavaFiles(filter));
    }

    // TODO: CharSequence instead?
    @Override public List<File> scanForFilesWithExtension(final String extension)
    {
        final List<File> files = new LinkedList<File>();

        scanForFilesWithExtension(files, this, extension);

        return files;
    }

    private void scanForFilesWithExtension(final List<File> files, final Folder root, final String extension)
    {
        files.addAll(root.getFilesWithExtension(extension));

        for(final Folder folder : root.getFolders())
        {
            scanForFilesWithExtension(files, folder, extension);
        }
    }

    @Override public List<File> scanForFiles(final FilenameFilter filter)
    {
        final List<File> files = new LinkedList<File>();

        scanForFiles(files, this, filter);

        return files;
    }

    private void scanForFiles(final List<File> files, final Folder root, final FilenameFilter filter)
    {
        files.addAll(root.getFiles(filter));

        for(final Folder folder : root.getFolders())
        {
            scanForFiles(files, folder, filter);
        }
    }

    @Override public List<File> scanForFiles(final FileFilter filter)
    {
        final List<File> files = new LinkedList<File>();

        scanForFiles(files, this, filter);

        return files;
    }

    private void scanForFiles(final List<File> files, final Folder root, final FileFilter filter)
    {
        files.addAll(root.getFiles(filter));

        for(final Folder folder : root.getFolders())
        {
            scanForFiles(files, folder, filter);
        }
    }



    @Override public Folder getFolder(final CharSequence path)
    {
        final java.io.File newFile =
            new java.io.File(_file, path.toString());

        if (newFile.exists() && !newFile.isDirectory())
        {
            return null;
        }

        return new Paths(newFile);
    }

    @Override public List<Folder> getFolders()
    {
        return asFolders(getJavaFiles(FOLDER_FILTER));
    }

    @Override public List<Folder> getFolders(final CharSequence pattern)
    {
        return asFolders(getJavaFiles(newFilter(pattern, Boolean.FALSE)));
    }

    @Override public List<Folder> getFolders(final Pattern pattern)
    {
        return asFolders(getJavaFiles(newFilter(pattern, Boolean.FALSE)));
    }

    private List<java.io.File> getJavaFiles(final FileFilter filter)
    {
        List<java.io.File> list = Collections.emptyList();

        if (isRealFolder())
        {
            final java.io.File[] files = _file.listFiles(filter);
            if (files != null && files.length > 0)
            {
                list = Arrays.asList(files);
            }
        }

        return list;
    }

    private FileFilter newFilter(final CharSequence pattern, final Boolean file)
    {
        return newFilter(Pattern.compile(pattern.toString()), file);
    }

    private FileFilter newFilter(final Pattern pattern, final Boolean file)
    {
        return new PatternFilter(pattern, file);
    }

    /**
     * Returns the absolute path
     *
     * @return the absolute path.
     *
     * @since 1.0
     */
    @Override public String toString()
    {
        return _file.getAbsolutePath();
    }

    @Override public boolean equals(final Object other)
    {
        if (other instanceof Path)
        {
            final Path path = (Path) other;

            return _file.equals(path.asJavaFile());
        }

        return false;
    }

    @Override public int hashCode()
    {
        return _file.hashCode();
    }

    private static final FileFilter FILE_FILTER = new FileFilter() {
        @Override public boolean accept(final java.io.File file) {
            return file.isFile();
        }
    };
    private static final FileFilter FOLDER_FILTER = new FileFilter() {
        @Override public boolean accept(final java.io.File file) {
            return file.isDirectory();
        }
    };


    private static final class PatternFilter implements FileFilter
    {
        private final Pattern pattern;
        private final Boolean files;

        public PatternFilter(final Pattern pattern, final Boolean files)
        {
            this.pattern = pattern;
            this.files = files;
        }

        public boolean accept(final java.io.File file)
        {
            final boolean result = false;

            if ((files && file.isFile()) || (!files && !file.isFile()))
            {
                // canonical path ?
                return pattern.matcher(file.getName()).matches();
            }

            return result;
        }
    }

}


/*
 * Copyright 2010 Eli Doran
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
